 1.Kafka高级特性解析之物理存储中磁盘存储的零拷贝
   
   kafka高性能，是多方面协同的结果，包括宏观架构、分布式partition存储、ISR数据同步、以及“无所不用其极”的
高效利用磁盘/操作系统特性。
   零拷贝并不是不需要拷贝，而是减少不必要的拷⻉次数。通常是说在IO读写过程中。
   nginx的高性能也有零拷贝的身影。
   传统IO
   比如：读取文件，socket发送
   传统方式实现：先读取、再发送，实际经过1~4 次copy。 
   buffer = File.read
   Socket.send(bufffer)
   1).第一次：将磁盘文件，读取到操作系统内核缓冲区；
   2).第二次：将内核缓冲区的数据，copy到application应用程序的buffer；
   3).第三步：将application应用程序buffer中的数据，copy到socket网络发送缓冲区(属于操作系统内核的缓冲区)
   4).第四次：将socket buffer的数据，copy到网络协议栈，由网卡进行网络传输。
   实际IO读写，需要进行IO中断，需要CPU响应中断(内核态到用户态转换)，尽管引入DMA(Direct Memory
Access，直接存储器访问)来接管CPU的中断请求，但四次copy是存在“不必要的拷贝”的。
   实际上并不需要第⼆个和第三个数据副本。数据可以直接从读缓冲区传输到套接字缓冲区。
   kafka的两个过程
   1).网络数据持久化到磁盘(Producer到Broker)
   2).磁盘文件通过网络发送(Broker到Consumer)
   数据落盘通常都是非实时的，Kafka的数据并不是实时的写⼊硬盘，它充分利用了现代操作系统分页存储来利用内
存提高I/O效率。
   磁盘文件通过网络发送(Broker到Consumer)
   磁盘数据通过DMA(Direct Memory Access，直接存储器访问)拷贝到内核态 Buffer
   直接通过 DMA 拷贝到 NIC Buffer(socket buffer)，无需 CPU 拷贝。
   除了减少数据拷贝外，整个读文件 ==> 网络发送由一个 sendfile 调⽤完成，整个过程只有两次上下文切换，因此
大大提高了性能。
   Java NIO对sendfile的支持就是FileChannel.transferTo()/transferFrom()。
   fileChannel.transferTo( position, count, socketChannel);
   把磁盘文件读取OS内核缓冲区后的fileChannel，直接转给socketChannel发送；底层就是sendfile。消费者从
broker读取数据，就是由此实现。
   具体来看，Kafka 的数据传输通过 TransportLayer 来完成，其子类 PlaintextTransportLayer 通过Java NIO 的
FileChannel 的 transferTo 和 transferFrom 方法实现零拷贝。
   注： transferTo 和 transferFrom 并不保证一定能使用零拷贝，需要操作系统支持。
   Linux 2.4+ 内核通过 sendfile 系统调用，提供了零拷贝。
 
 2.页缓存
 
   页缓存是操作系统实现的一种主要的磁盘缓存，以此用来减少对磁盘 I/O 的操作。
   具体来说，就是把磁盘中的数据缓存到内存中，把对磁盘的访问变为对内存的访问。
   Kafka接收来自socket buffer的网络数据，应用进程不需要中间处理、直接进⾏持久化时。可以使用mmap内存文
件映射。
   Memory Mapped Files
   简称mmap，简单描述其作用就是：将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。
   它的工作原理是直接利用操作系统的Page来实现磁盘文件到物理内存的直接映射。完成映射之后你对物理内存的
操作会被同步到硬盘上（操作系统在适当的时候）。
   通过mmap，进程像读写硬盘一样读写内存（当然是虚拟机内存）。使用这种方式可以获取很大的I/O提升，省去
了用户空间到内核空间复制的开销。
   mmap也有一个很明显的缺陷：不可靠，写到mmap中的数据并没有被真正的写到硬盘，操作系统会在程序主动
调用flush的时候才把数据真正的写到硬盘。
   Kafka提供了一个参数producer.type 来控制是不是主动flush；
   如果Kafka写入到mmap之后就立即flush然后再返回Producer叫同步(sync)；
   写入mmap之后立即返回Producer不调用flush叫异步(async)。
   Java NIO对文件映射的支持
   Java NIO，提供了一个MappedByteBuffer 类可以用来实现内存映射。
   MappedByteBuffer只能通过调用FileChannel的map()取得，再没有其他方式。
   FileChannel.map()是抽象方法，具体实现是在 FileChannelImpl.map()可自行查看JDK源码，其map0()方法就是
调用了Linux内核的mmap的API。
   使用 MappedByteBuffer类要注意的是
   mmap的文件映射，在full gc时才会进行释放。当close时，需要手动清除内存映射文件，可以反射调用
sun.misc.Cleaner方法。
   当一个进程准备读取磁盘上的文件内容时：
   1).操作系统会先查看待读取的数据所在的页 (page)是否在页缓存(pagecache)中，如果存在(命中)则直接返回数
据，从而避免了对物理磁盘的 I/O 操作；
   2).如果没有命中，则操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存，之后再将数据返回给进程。
   如果一个进程需要将数据写入磁盘：
   1).操作系统也会检测数据对应的页是否在页缓存中，如果不存在，则会先在页缓存中添加相应的页，最后将数
据写入对应的页。
   2).被修改过后的页也就变成了脏页，操作系统会在合适的时间把脏页中的数据写入磁盘，以保持数据的一致性。
   对一个进程而言，它会在进程内部缓存处理所需的数据，然而这些数据有可能还缓存在操作系统的页缓存中，因
此同一份数据有可能被缓存了两次。并且，除非使用Direct I/O的方式， 否则页缓存很难被禁止。
   当使用页缓存的时候，即使Kafka服务重启， 页缓存还是会保持有效，然而进程内的缓存却需要重建。这样也极
大地简化了代码逻辑，因为维护页缓存和文件之间的一致性交由操作系统来负责，这样会比进程内维护更加安全有
效。
   Kafka中大量使用了页缓存，这是 Kafka 实现高吞吐的重要因素之一。
   消息先被写入页缓存，由操作系统负责刷盘任务。
 
 3.顺序写入
   
   操作系统可以针对线性读写做深层次的优化，比如预读(read-ahead，提前将一个比较大的磁盘块读入内存) 和后
写(write-behind，将很多小的逻辑写操作合并起来组成一个大的物理写操作)技术。
   Kafka 在设计时采用了文件追加的方式来写入消息，即只能在日志文件的尾部追加新的消 息，并且也不允许修改
已写入的消息，这种⽅式属于典型的顺序写盘的操作，所以就算 Kafka 使⽤磁盘作为存储介质，也能承载⾮常⼤的吞
吐量。
   mmap和sendfile：
   1).Linux内核提供、实现零拷贝的API；
   2).sendfile 是将读到内核空间的数据，转到socket buffer，进行网络发送；
   3).mmap将磁盘文件映射到内存，支持读和写，对内存的操作会反映在磁盘⽂件上。
   4).RocketMQ 在消费消息时，使用了 mmap。kafka 使用了 sendFile。
   Kafka速度快是因为：
   1).partition顺序读写，充分利用磁盘特性，这是基础；
   2).Producer生产的数据持久化到broker，采用mmap文件映射，实现顺序的快速写入；
   3).Customer从broker读取数据，采用sendfile，将磁盘文件读到OS内核缓冲区后，直接转到socket buffer进
行网络发送。
   